---
title: 垃圾收集器
---

1. 那些内存需要回收
2. 什么时候回收
3. 如何回收


在 Java 内存运行时区域的各个部分中，程序计数器、虚拟机栈、本地方法栈 3 个区域和线程的生命周期相同，这几个区域内存的分配和回收都具备确定性，当方法结束或者线程结束时，内存就自然跟着回收了。

而 Java 堆和方法区这两个区域则有着明显的不确定性，一个接口的多个实现类需要的内存可能会不一样，一个方法所执行的不同条件分支所需要的内存也可能不一样，只有处于运行期间，才能知道程序究竟会创建那些对象，创建多少个对象。这部分内存分配和回收是动态的。
垃圾收集器关注的正是这部分内存如何分配和回收。
## 那些内存需要回收
垃圾收集器在对堆进行回收前，第一件事情就是要确定这些对象之中那些还”存活者”，那些已经“死去”（即不可能再被任何途径使用的对象）。
### 引用计数法
在对象中添加一个引用计数器，每有一个地方引用它时，计数器值就加一；当引用失效时，计数器值就减一；任何时刻计数器为零的对象就是不可能再被使用的。

在主流的 Java 虚拟机里面没有选用引用计数算法进行内存管理，主要原因是，这个看似简单的算法有很多例外情况需要考虑。必须要配合大量的额外处理才能保证正确地工作，比如单纯的引用计数算法无法解决对象之间循环引用的问题。
### 可达性分析算法
当前主流的商业程序语言的内存管理系统，都是通过可达性分析算法来判定对象是否存活的。

这个算法的基本思路就是通过一系列称为 `GC Roots` 的根路径作为起始节点集，从这些节点开始，根据引用向下搜索，搜索过程所走的路径称为引用链（Reference Chain），如果某个对象到 GC Roots 间没有任何引用链相连，或者用图论的话来说就是 GC Roots 到这个对象不可达时，则证明此对象是不可能再被使用的。

![image-20230326173651033](https://img.wkq.pub/pic/image-20230326173651033.png)

在 Java 技术体系里面，固定可作为 GC Roots 的对象包括以下几种：

* 在虚拟机栈（栈帧中的本地变量表）中引用的对象，比如当前正在运行的方法所用到的参数、局部变量、临时变量等。
* 在方法区中静态属性引用的对象，比如 Java 类的引用类型静态变量。
* 在方法区中常量引用的对象，比如字符串常量池里的引用。
* 在本地方法栈中 JNI 引用的对象
* Java 虚拟机内部的引用，如基本数据类型对应的 Class 对象，一些常驻的异常对象（比如 NullPointException、OutofMemoryError）等，还有系统加载类。
* 所有被 同步锁（synchronized 关键字）持有的对象。
* 反映 Java 虚拟机内部情况的 JMXBean。

### 再谈引用

无论是通过引用计数算法判断对象的引用数量，还是通过可达性分析算法判断对象是否引用链可达，判定对象的存活都和引用离不开关系。



在 JDK 1.2 之前，Java 里面的引用是很传统的定义：如果 reference 类型的数据中存储的数值代表的是另一块内存的起始地址，就称该 reference 数据是代表某块内存、某个对象的引用。



在 JDK 1.2 之后，Java 对引用的概念进行了扩充，将引用分为强引用（strongly Reference）、软引用（Soft Reference）、弱引用（Weak Reference）和虚引用（Phantom Reference）。这四种引用强度逐渐减弱。

1. 强引用是最传统的引用定义，类似 Object obj = new Object();这种引用关系。无论任何情况下，只要强引用关系存在，垃圾收集器永远不回回收掉被引用的对象。
2. 软引用时用来描述一些还有用，但非必须的对象。只被软引用关联着的对象，在系统将要发生内存溢出异常前，会把这些对象列进回收范围内进行第二次回收，如果这次回收还没有充足得内存，才会抛出内存异常异常。
3. 弱引用也是用来描述那些非必须对象，但是它的强度要比弱引用更弱一些，被弱引用关联的对象只能生存到下一次垃圾收集发生为止。
4. 虚引用是一种最弱的引用关系，一个对象是否有虚引用的存在，完全不对其生存周期构成影响，也无法通过虚引用来取得一个对象实例。

